﻿using System;
using System.Drawing;
using MapCenterSystem.DefineType;

namespace MapCenterSystem.Utility
{
    public class LocateConvert
    {
        const double MinLatitude = -85.05112878;
        const double MaxLatitude = 85.05112878;
        const double MinLongitude = -177;
        const double MaxLongitude = 177;
        static Size tileDefaultSize = new Size(256, 256);

        private static double Clip(double n, double minValue, double maxValue)
        {
            return Math.Min(Math.Max(n, minValue), maxValue);
        }

        private static Size GetTileMatrixSizePixel(int zoom)
        {
            Size s = GetTileMatrixSizeXY(zoom);
            return new Size(s.Width * tileDefaultSize.Width, s.Height * tileDefaultSize.Height);
        }

        private static Size GetTileMatrixMaxXY(int zoom)
        {
            int xy = (1 << zoom);
            return new Size(xy - 1, xy - 1);
        }

        private static Size GetTileMatrixMinXY(int zoom)
        {
            return new Size(0, 0);
        }

        private static Size GetTileMatrixSizeXY(int zoom)
        {
            Size sMin = GetTileMatrixMinXY(zoom);
            Size sMax = GetTileMatrixMaxXY(zoom);

            return new Size(sMax.Width - sMin.Width + 1, sMax.Height - sMin.Height + 1);
        }

        public static Point ToPoint(Coordinate geoCoordinate, int zoom)
        {
            Point ret = Point.Empty;
            geoCoordinate.Latitude = Clip(geoCoordinate.Latitude, MinLatitude, MaxLatitude);
            geoCoordinate.Longitude = Clip(geoCoordinate.Longitude, MinLongitude, MaxLongitude);

            double x = (geoCoordinate.Longitude+ 180) / 360;
            double sinLatitude = Math.Sin(geoCoordinate.Latitude * Math.PI / 180);
            double y = 0.5 - Math.Log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI);

            Size s = GetTileMatrixSizePixel(zoom);
            int mapSizeX = s.Width;
            int mapSizeY = s.Height;

            ret.X = (int)Clip(x * mapSizeX + 0.5, 0, mapSizeX - 1);
            ret.Y = (int)Clip(y * mapSizeY + 0.5, 0, mapSizeY - 1);

            return ret;
        }

        public static Point ToPoint(double longitude, double latitude, int zoom)
        {
            Point ret = Point.Empty;
            latitude = Clip(latitude, MinLatitude, MaxLatitude);
            longitude = Clip(longitude, MinLongitude, MaxLongitude);

            double x = (longitude + 180) / 360;
            double sinLatitude = Math.Sin(latitude * Math.PI / 180);
            double y = 0.5 - Math.Log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI);

            Size s = GetTileMatrixSizePixel(zoom);
            int mapSizeX = s.Width;
            int mapSizeY = s.Height;

            ret.X = (int)Clip(x * mapSizeX + 0.5, 0, mapSizeX - 1);
            ret.Y = (int)Clip(y * mapSizeY + 0.5, 0, mapSizeY - 1);

            return ret;

        }

        public static Coordinate ToGeographicCoordinate(Point point, int zoom)
        {
            Coordinate ret = new Coordinate();
            Size s = GetTileMatrixSizePixel(zoom);
            double mapSizeX = s.Width;
            double mapSizeY = s.Height;
            double xx = (Clip(point.X, 0, mapSizeX - 1) / mapSizeX) - 0.5;
            double yy = 0.5 - (Clip(point.Y, 0, mapSizeY - 1) / mapSizeY);
            ret.Latitude = 90 - 360 * Math.Atan(Math.Exp(-yy * 2 * Math.PI)) / Math.PI;
            ret.Longitude = 360 * xx;
            return ret;
        }

        public static Coordinate ToGeographicCoordinate(int X, int Y, int zoom)
        {
            Coordinate ret = new Coordinate();

            Size s = GetTileMatrixSizePixel(zoom);
            double mapSizeX = s.Width;
            double mapSizeY = s.Height;

            double xx = (Clip(X, 0, mapSizeX - 1) / mapSizeX) - 0.5;
            double yy = 0.5 - (Clip(Y, 0, mapSizeY - 1) / mapSizeY);

            ret.Latitude = 90 - 360 * Math.Atan(Math.Exp(-yy * 2 * Math.PI)) / Math.PI;
            ret.Longitude = 360 * xx;
            return ret;
        }

        public static Point ToTile(double longitude, double latitude, int zoom)
        {
            Point result = ToPoint(longitude, latitude, zoom);
            result.X /= 256;
            result.Y /= 256;
            return result;
        }

        public static Point ToTile(Coordinate coordiante, int zoom)
        {
            Point result = ToPoint(coordiante, zoom);
            result.X /= 256;
            result.Y /= 256;
            return result;
        }

        public static Point ToTile(Point globalPoint, int zoom)
        {
            Coordinate coordinate = ToGeographicCoordinate(globalPoint, zoom);
            return ToTile(coordinate, zoom);
        }
    }
}
